Latest update: November 2014
This tutorial covers the creation of a routine to issue iSDIO commands, before we move on to operating the FlashAir card.
Important revision | (Sep. 29 2014): | Added sending dummy data at Sd2CardExt::readExt() function. |
(Nov. 2 2017): | Added CMD17 usage method to Sd2CardExt::readExt () function. | |
Added CMD24 usage method to Sd2CardExt::writeExt () function. |
In order to issue commands, we'll use the Extension Command Registers CMD48 (READ_EXTR_SINGLE) and CMD49 (WRITE_EXTR_SINGLE), which are defined in Function Extension Commands (class 11) of SD standard(*1).
CMD48 and CMD49 have three modes:
The iSDIO Wireless LAN Simplified Addendum regulates which mode should be used where (e.g. Register mode for address area A, Data Port mode for address area B).
The Arduino IDE has an SD card library available by default, which consists of three modules:
For this tutorial we're only going to need Sd2Card, however out of the box it doens't support CMD48/49. So lets add them!
When you finish installing Arduino IDE, you will see an "Arduino" folder in the Documents directory. Inside that is a "libraries" directory.
We'll be copying Sd2Card here, then adding iSDIO support to create our own module.
The Directory Structure will look something like the following:
<Document>/
+-- Arduino
+-- libraries
+-- iSDIO
+-- iSdio.h *1
+-- iSdio.cpp *2
+-- utility
+-- Sd2Card.h *1
+-- Sd2Card.cpp *1
+-- Sd2CardExt.h *2
+-- Sd2CardExt.cpp *2
+-- Sd2PinMap.h *1
+-- SdInfo.h *1
Files marked with
1 are copied from the base version (
Program Files (x86)\Arduino\libraries\SD\utility
). We'll be making a small change to
Sd2Card.h.
Files marked with 2 are going to be created now.
We're going to make an Sd2CardExt class by extending the base Sd2Card class and adding CMD48/49. We're calling it Sd2CardExt because CMD48/49 are technically part of the SD standard, not the iSDIO standard.
We're adding the CMD48/49 modes we discussed above (lines 10-14), and then two basic read/write functions (lines 16 and 17).
#ifndef Sd2CardExt_h
#define Sd2CardExt_h
#include "Sd2Card.h"
class Sd2CardExt : public Sd2Card {
public:
Sd2CardExt(void) : Sd2Card() {}
uint8_t readExtDataPort(uint8_t mio, uint8_t func, uint16_t addr, uint8_t* dst);
uint8_t readExtMemory(uint8_t mio, uint8_t func, uint32_t addr, uint16_t count, uint8_t* dst);
uint8_t writeExtDataPort(uint8_t mio, uint8_t func, uint16_t addr, const uint8_t* src);
uint8_t writeExtMemory(uint8_t mio, uint8_t func, uint32_t addr, uint16_t count, const uint8_t* src);
uint8_t writeExtMask(uint8_t mio, uint8_t func, uint32_t addr, uint8_t mask, const uint8_t* src);
protected:
uint8_t readExt(uint32_t arg, uint8_t* src, uint16_t count);
uint8_t writeExt(uint32_t arg, const uint8_t* src, uint16_t count);
};
#endif // Sd2CardExt_h
To inherit the Sd2Card class, Sd2Card.h needs to use "protected". See below.
Before:
...
private:
uint32_t block_;
uint8_t chipSelectPin_;
uint8_t errorCode_;
...
After:
...
protected:
uint32_t block_;
uint8_t chipSelectPin_;
uint8_t errorCode_;
...
First, add constants related to CMD48/49.
#include <Arduino.h>
#include "Sd2CardExt.h"
uint8_t const CMD48 = 0x30;
uint8_t const CMD49 = 0x31;
uint8_t const SD_CARD_ERROR_CMD48 = 0x80;
uint8_t const SD_CARD_ERROR_CMD49 = 0x81;
Next, make functions to send/receive data using SPI. This will be the same as the version in the Sd2Card module, so we're going to use inline to copy it (because it will be used frequently).
/** Send a byte to the card */
inline void spiSend(uint8_t b) {
SPDR = b;
while (!(SPSR & (1 << SPIF)));
}
/** Receive a byte from the card */
inline uint8_t spiReceive(void) {
spiSend(0xFF);
return SPDR;
}
Next, we'll make the common
readExt()
and
writeExt()
functions. These will issue CMD48/49 commands using
readData()
and
writeBlock()
, which are functions for CMD17/24 included in the base Sd2Card class.
The signal read/write timing of CMD48/49 is the same as CMD17/24 for general memory access, however the command number and parameters are different and CMD48/49 read/writes are always in 512 byte blocks.
If you want to read/write less than 512 bytes you'll need to add padding.
In
readExt()
, one byte of dummy data is sent after
chipSelectHigh()
. This is to ensure that command processing starts properly.
/** Perform Extention Read. */
uint8_t Sd2CardExt::readExt(uint32_t arg, uint8_t* dst, uint16_t count) {
uint16_t i;
// send command and argument.
if (cardCommand(CMD48, arg) && cardCommand(CMD17, arg)) {
error(SD_CARD_ERROR_CMD48);
goto fail;
}
// wait for start block token.
if (!waitStartBlock()) {
goto fail;
}
// receive data
for (i = 0; i < count; ++i) {
dst[i] = spiReceive();
}
// skip dummy bytes and 16-bit crc.
for (; i < 514; ++i) {
spiReceive();
}
chipSelectHigh();
spiSend(0xFF); // dummy clock to force FlashAir finish the command.
return true;
fail:
chipSelectHigh();
return false;
}
/** Perform Extention Write. */
uint8_t Sd2CardExt::writeExt(uint32_t arg, const uint8_t* src, uint16_t count) {
uint16_t i;
uint8_t status;
// send command and argument.
if (cardCommand(CMD49, arg) && cardCommand(CMD24, arg)) {
error(SD_CARD_ERROR_CMD49);
goto fail;
}
// send start block token.
spiSend(DATA_START_BLOCK);
// send data
for (i = 0; i < count; ++i) {
spiSend(src[i]);
}
// send dummy bytes until 512 bytes.
for (; i < 512; ++i) {
spiSend(0xFF);
}
// dummy 16-bit crc
spiSend(0xFF);
spiSend(0xFF);
// wait a data response token
status = spiReceive();
if ((status & DATA_RES_MASK) != DATA_RES_ACCEPTED) {
error(SD_CARD_ERROR_WRITE);
goto fail;
}
// wait for flash programming to complete
if (!waitNotBusy(SD_WRITE_TIMEOUT)) {
error(SD_CARD_ERROR_WRITE_TIMEOUT);
goto fail;
}
chipSelectHigh();
return true;
fail:
chipSelectHigh();
return false;
}
Lastly, we'll add functions to handle the three read/write modes. Make a parameter for each mode and
pass it
to
readExt()
and
writeExt()
, along with the data size.
mio
is a flag to indicate memory area or I/O area. With iSDIO, the I/O area should always be
flagged.
func
is a function number. With iSDIO, function number 0 is reserved for general
information, so the
actual function number always starts at one. For example, FlashAir's wireless LAN function is 1.
addr
is a memory address.
Address space is divided into 512 bytes "pages". In register mode, you can access individual parts of a page, but in data port mode the entire page is accessed at once. Also, cross-page access is not available.
count
is the number of byte to access. If you're in data port mode, you must set it to 0.
For more information, see section 5.7.2 of SD Specifications Part 1 Physical Layer Simplified Specification.
uint8_t Sd2CardExt::readExtDataPort(uint8_t mio, uint8_t func,
uint16_t addr, uint8_t* dst) {
uint32_t arg =
(((uint32_t)mio & 0x1) << 31) |
(mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
(((uint32_t)addr & 0x1FE00) << 9);
return readExt(arg, dst, 512);
}
uint8_t Sd2CardExt::readExtMemory(uint8_t mio, uint8_t func,
uint32_t addr, uint16_t count, uint8_t* dst) {
uint32_t offset = addr & 0x1FF;
if (offset + count > 512) count = 512 - offset;
if (count == 0) return true;
uint32_t arg =
(((uint32_t)mio & 0x1) << 31) |
(mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
((addr & 0x1FFFF) << 9) |
((count - 1) & 0x1FF);
return readExt(arg, dst, count);
}
uint8_t Sd2CardExt::writeExtDataPort(uint8_t mio, uint8_t func,
uint16_t addr, const uint8_t* src) {
uint32_t arg =
(((uint32_t)mio & 0x1) << 31) |
(mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
(((uint32_t)addr & 0x1FE00) << 9);
return writeExt(arg, src, 512);
}
uint8_t Sd2CardExt::writeExtMemory(uint8_t mio, uint8_t func,
uint32_t addr, uint16_t count, const uint8_t* src) {
uint32_t arg =
(((uint32_t)mio & 0x1) << 31) |
(mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
((addr & 0x1FFFF) << 9) |
((count - 1) & 0x1FF);
return writeExt(arg, src, count);
}
uint8_t Sd2CardExt::writeExtMask(uint8_t mio, uint8_t func,
uint32_t addr, uint8_t mask, const uint8_t* src) {
uint32_t arg =
(((uint32_t)mio & 0x1) << 31) |
(mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
(0x1 << 26) |
((addr & 0x1FFFF) << 9) |
mask;
return writeExt(arg, src, 1);
}
iSDIO is a command protocol, it's meant to pass content for a whole range of applications. This means that unfortunately it's not the easiest to use, so lets make some helper functions to keep things simple and organized!
put\_xxx()
writes the buffer and moves the pointer ahead by the buffer's size. It returns
the new pointer
as well.
put\_xxx_arg()
is for script parameters, and writes the size of "value", "value" itself, and
padding
to fit the 4 byte border. It will also move the pointer forwards by the number of bytes written and
return
it.
put\_command\_header()
and
put\_command\_info\_header()
are used to make headers for the script. It will also move the
pointer
forwards by the number of bytes written and return it.
get\_xxx()
reads from the buffer.
We will explain these functions in more detail in future tutorials.
uint8_t* put_u8(uint8_t* p, uint8_t value);
uint8_t* put_u16(uint8_t* p, uint16_t value);
uint8_t* put_u32(uint8_t* p, uint32_t value);
uint8_t* put_str(uint8_t* p, const char* value);
uint8_t* put_u8_arg(uint8_t* p, uint8_t value);
uint8_t* put_u16_arg(uint8_t* p, uint16_t value);
uint8_t* put_u32_arg(uint8_t* p, uint32_t value);
uint8_t* put_str_arg(uint8_t* p, const char* value);
uint8_t* put_command_header(uint8_t* p, uint8_t num_commands,
uint32_t command_bytes);
uint8_t* put_command_info_header(uint8_t* p, uint16_t command_id,
uint32_t sequence_id, uint16_t num_args);
uint8_t get_u8(uint8_t* p);
uint16_t get_u16(uint8_t* p);
uint32_t get_u32(uint8_t* p);
Start the Arduino IDE, select Sketch > Use Library > Add Library ... from the menu, then set the iSDIO folder. Once that is done, when select Sketch > Use Library > iSDIO should appear in the menu automatically.
First, let's make sure the library is compiled properly.
Select File > New File from the menu and open new editor. Next, select Sketch > Use Library > iSDIO.
Make
setup()
function and blank
loop()
function to initialize the card.
#include <utility/Sd2CardExt.h>
const int chipSelectPin = 4;
Sd2CardExt card;
void setup() {
// Initialize UART for message print.
Serial.begin(9600);
while (!Serial) {
;
}
// Initialize SD card.
Serial.print(F("\nInitializing SD card..."));
if (card.init(SPI_HALF_SPEED, chipSelectPin)) {
Serial.print(F("OK"));
} else {
Serial.print(F("NG"));
abort();
}
}
void loop() {
}
Then, make sure no errors occur with Sketch > Verification/Compilation.
arduino_tutorial_02.zip (22KB)
All sample code on this page is licensed under BSD 2-Clause License.